﻿/*
 *	Copyright (c) 2009 Queensland University of Technology. All rights reserved.
 *	The QUT Bioinformatics Collection is open source software released under the 
 *	Microsoft Public License (Ms-PL): http://www.microsoft.com/opensource/licenses.mspx.
 */
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml.Linq;
using QUT.Bio.Util;
using System.Threading;
using System.Windows.Threading;

namespace BlastExplorer {
    /// <summary>
    /// Interaction logic for TaxonSelector.xaml
    /// </summary>

    public partial class TaxonSelector : UserControl {

        private static Taxon allTaxa;
        private static Taxon[] taxa;
        private static bool loadComplete;
        private static event Action LoadComplete;
        private static readonly char[] PARENTHESES = { '(', ')' };
        private static readonly char[] TaxonSeparators = { ',', '\n' };
        private const int MIN_LETTERS_TO_SHOW = 3;
        private readonly ObservableCollection<Taxon> visibleTaxa = new ObservableCollection<Taxon>();
        private List<Taxon> selectedTaxa = new List<Taxon>();

        // TODO: See if I can improve the performance of load and maybe memory footprint.

        #region Public method: LoadTaxa
        /// <summary>
        /// Loads a cut-down copy of taxonomy database to support autocomplete on the
        /// text field.
        /// </summary>

        public static void LoadTaxa () {
            Thread t = new Thread( LoadTaxaImplementation );
            t.Start();
        }
        #endregion

        #region Private method: LoadTaxaImplementation
        /// <summary>
        ///	Load taxonomy database, expected to be called on a separate thread
        ///	so we don't block the user interface.
        /// </summary>

        private static void LoadTaxaImplementation () {
            try {
                XDocument doc = XmlResource( "taxonomy.xml.gz" );
                allTaxa = LoadTaxaRecursive( doc.Root );
                List<Taxon> taxa = new List<Taxon>();
                Load( allTaxa, taxa );
                taxa.Sort();
                TaxonSelector.taxa = taxa.ToArray();
                loadComplete = true;

                if ( LoadComplete != null ) {
                    Application.Current.Dispatcher.BeginInvoke( LoadComplete );
                }
            }
            catch ( Exception e ) {
                Alert.Show( "Error loading taxonomy data: {0}", e );
            }
        }
        #endregion

        #region Private method: LoadTaxaRecursive
        /// <summary>
        /// Creates and recursively populates a Taxon object from a Xml subtree.
        /// </summary>
        /// <param name="root">The subtree from which taxon is obtained.</param>
        /// <returns>A Taxon object containing the contents of the taxonomic subtree encoded in the root element.</returns>

        private static Taxon LoadTaxaRecursive ( XElement root ) {
            Taxon taxon = new Taxon {
                TaxId = (decimal) root.Attribute( "I" ),
                ScientificName = (string) root.Attribute( "S" ),
                GenbankCommonName = (string) root.Attribute( "G" )
            };

            foreach ( XElement childElement in root.Nodes() ) {
                taxon.Children.Add( LoadTaxaRecursive( childElement ) );
            }

            return taxon;
        }
        #endregion

        #region Private method: XmlResource
        /// <summary>
        /// Loads an Xml document from the specified filename.
        /// </summary>
        /// <param name="fileName">The name of the xml file to load from.</param>
        /// <returns></returns>

        private static XDocument XmlResource ( string fileName ) {
			using ( StreamReader r = new StreamReader( new GZipStream( new FileStream( fileName, FileMode.Open ), CompressionMode.Decompress ) ) ) {
				return XDocument.Load( r );
			}
		}
        #endregion

        #region Constructor
        /// <summary>
        /// Initialises a new instance of TaxonSelector.
        /// </summary>

        public TaxonSelector () {
            InitializeComponent();

            if ( loadComplete ) {
                SetListItems();
            }
            else {
                LoadComplete += SetListItems;
            }

            nameField.TextChanged += TextChanged;
        }
        #endregion

        #region Private method: SetListItems
        /// <summary>
        /// Replaces the contents of the list box with the presently visible taxa.
        /// </summary>

        private void SetListItems () {
            listbox.Items.Clear();
            listbox.ItemsSource = visibleTaxa;
        }
        #endregion

        #region Property: SelectedTaxa
        /// <summary>
        /// Get or set the currently selected taxa.
        /// </summary>

        public Taxon[] SelectedTaxa {
            get {
                return selectedTaxa.ToArray();
            }
            set {
                selectedTaxa.Clear();

                if ( value != null ) {
                    selectedTaxa.AddRange( value );
                }

                nameField.Text = selectedTaxa.Join( ", " );
            }
        }
        #endregion

        #region Private method: Load
        /// <summary>
        /// Flattens a taxonomic tree into a list.
        /// </summary>
        /// <param name="tree">The tree that is to be transformed to a list.</param>
        /// <param name="taxa">The list that will hold the results.</param>

        private static void Load ( Taxon tree, List<Taxon> taxa ) {
            taxa.Add( tree );

            foreach ( Taxon child in tree.Children ) {
                Load( child, taxa );
            }
        }
        #endregion

        #region Private method: TextChanged
        /// <summary>
        /// Update the contents of the list box based on the prefix entered in the text box.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

        private void TextChanged ( object sender, TextChangedEventArgs e ) {
            if ( !loadComplete ) {
                return;
            }

            if ( ignoreTextChange ) {
                return;
            }

            string[] taxonNames = nameField.Text.Trim().Split( TaxonSeparators, StringSplitOptions.RemoveEmptyEntries );

            visibleTaxa.Clear();

            if ( taxonNames.Length == 0 ) {
                selectedTaxa.Clear();
                return;
            }

            List<string> taxonNameList = new List<string>( from x in taxonNames
                                                           select x.Trim().ToLower() );

            for ( int i = selectedTaxa.Count - 1; i >= 0; i-- ) {
                string taxonName = selectedTaxa[i].ToString().ToLower();

                if ( !taxonNameList.Contains( taxonName ) ) {
                    selectedTaxa.RemoveAt( i );
                }
            }

            string name = taxonNames[taxonNames.Length - 1].Trim().ToLower();

            if ( name.Length < MIN_LETTERS_TO_SHOW ) {
                return;
            }

            string[] nameParts = name.Split( PARENTHESES, StringSplitOptions.RemoveEmptyEntries );

            Action refreshTaxonList = delegate() {
                RefreshTaxonList( nameParts[0], name );
            };

            if ( loadComplete ) {
                refreshTaxonList();
            }
            else {
                LoadComplete += refreshTaxonList;
            }
        }
        #endregion

        #region Method: RefreshTaxonList
        /// <summary>
        /// Inserts all tax into the list that start with the name as entered by the user.
        /// </summary>
        /// <param name="scientificName">The name part of the string entered by the user.</param>
        /// <param name="nameAsEntered">The prefix entered by user in the text field. This is assumed to be in lower case.</param>

        private void RefreshTaxonList ( string scientificName, string nameAsEntered ) {
            Taxon query = new Taxon {
                ScientificName = scientificName
            };

            int location = Array.BinarySearch( taxa, query );

            if ( location < 0 ) {
                location = ~location;
            }

            for ( int i = location; i < taxa.Length; i++ ) {
                if ( taxa[i].ToString().ToLower().StartsWith( nameAsEntered ) ) {
                    visibleTaxa.Add( taxa[i] );
                }
                else {
                    break;
                }
            }
        }
        #endregion

        #region Method: SelectedTaxonChanged - event listener for the taxon list box
        bool ignoreTextChange = false;
        bool ignoreSelectionChange = false;

        private void SelectedTaxonChanged ( object sender, SelectionChangedEventArgs e ) {
            if ( ignoreSelectionChange ) {
                return;
            }

            ignoreTextChange = true;
            Taxon selectedTaxon = (Taxon) listbox.SelectedItem;

            if ( selectedTaxon != null ) {
                if ( !selectedTaxa.Contains( selectedTaxon ) ) {
                    selectedTaxa.Add( selectedTaxon );
                }

                nameField.Text = selectedTaxa.Join( "\n" ) + "\n";
            }
            ignoreTextChange = false;
        }
        #endregion
    }
}
